package com.superatom.springframework.core.io.support;

import com.superatom.springframework.core.annotation.AnnotationAwareOrderComparator;
import com.superatom.springframework.core.io.UrlResource;
import com.superatom.springframework.lang.Nullable;
import com.superatom.springframework.util.Assert;
import com.superatom.springframework.util.ClassUtils;
import com.superatom.springframework.util.ConcurrentReferenceHashMap;
import com.superatom.springframework.util.LinkedMultiValueMap;
import com.superatom.springframework.util.MultiValueMap;
import com.superatom.springframework.util.ReflectionUtils;
import com.superatom.springframework.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @author Kwokchiu Wong
 * @Title:
 * @Package
 * @Description: SpringFactoriesLoader工厂加载机制是Spring内部提供的一个约定俗成的加载方式，与java spi类似，
 * 只需要在模块的META-INF/spring.factories文件，这个Properties格式的文件中的key是接口、注解、或抽象类的全名，value是以逗号","分隔的实现类，
 * 使用SpringFactoriesLoader来实现相应的实现类注入Spring容器中
 *
 * @date 2020/10/16 4:21 下午
 */
public final class SpringFactoriesLoader {
    /**
     * The location to look for factories.
     * <p>Can be present in multiple JAR files.
     */
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);

    // 已类加载器为key，此加载器所加载的类（接口和实现类）为value
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

    private SpringFactoriesLoader() {
    }

    /**
     * Load and instantiate the factory implementations of the given type from
     * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
     * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
     * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
     * to obtain all registered factory names.
     * @param factoryClass the interface or abstract class representing the factory
     * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
     * @throws IllegalArgumentException if any factory implementation class cannot
     * be loaded or if an error occurs while instantiating any factory
     * @see #loadFactoryNames
     */
    public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
        Assert.notNull(factoryClass, "'factoryClass' must not be null");
        ClassLoader classLoaderToUse = classLoader;
        if (classLoaderToUse == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }
        List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
        if (logger.isTraceEnabled()) {
            logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
        }
        List<T> result = new ArrayList<>(factoryNames.size());
        for (String factoryName : factoryNames) {
            result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
        }
        AnnotationAwareOrderComparator.sort(result);
        return result;
    }

    public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
        String factoryClassName = factoryClass.getName();
        // 查找已经加载的类中是否含有ApplicationContextInitializer
        return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
    }

    /**
     * 把spring.factories里列出的类分类存储
     * @param classLoader
     * @return
     */
    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        MultiValueMap<String, String> result = cache.get(classLoader);
        // 如果此加载器已经在缓存中，马上返回
        if (result != null) {
            return result;
        }

        /**
         * 把spring.factories里列出的类分类存储（具体没做什么）
         */
        try {
            // Enumeration类，可枚举对象集合中的元素。（现在比较常用的是迭代器）
            // 使用加载器加载所有META-INF/spring.factories文件
            Enumeration<URL> urls = (classLoader != null ?
                    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
            result = new LinkedMultiValueMap<>();
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryClassName = ((String) entry.getKey()).trim();
                    for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                        result.add(factoryClassName, factoryName.trim());
                    }
                }
            }
            cache.put(classLoader, result);
            return result;
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
    }

    private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
        try {
            Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
            if (!factoryClass.isAssignableFrom(instanceClass)) {
                throw new IllegalArgumentException(
                        "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
            }
            return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
        }
        catch (Throwable ex) {
            throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
        }
    }
}
